﻿#include <cstdio>
#include <iostream>
#include <inttypes.h>

#if defined(__cplusplus)
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>

#include <libavfilter/avfilter.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>

#include <libavutil/opt.h>
#include <libavutil/avutil.h>
#include <libavutil/ffversion.h>
#include <libavutil/macros.h>
#include <libavutil/bprint.h>
#if defined(__cplusplus)
}
#endif

//声音采样率
constexpr auto AUDIO_INPUT_SAMPLERATE = 44100;
#define AUDIO_INPUT_CHANNEL_LAYOUT AV_CH_LAYOUT_MONO

typedef struct VolDetectContext {
    /**
     * Number of samples at each PCM value.
     * histogram[0x8000 + i] is the number of samples at value i.
     * The extra element is there for symmetry.
     */
    uint64_t histogram[0x10001];
} VolDetectContext;

typedef struct VolDetectValue{

    int nbSamples;
    int power;
    double meanVolumedB = 0;
    int maxVolume = 0;
    double maxVolumedB = 0;

}VolDetectValue;


// 错误处理宏
#define CHECK_RESULT(call, msg) do { \
    int result = call; \
    if (result < 0) { \
        std::cerr << "Error: " << msg << ": " << av_err2str(result) << std::endl; \
        exit(EXIT_FAILURE); \
    } \
} while (0)


/// <summary>
/// 查找音频线程
/// </summary>
/// <returns></returns>
int FindAudioStream(const AVFormatContext* avFmtCxt)
{
    if (avFmtCxt == NULL) { printf("this->avFmtCxt is null\n"); return -1; }

    if (avFmtCxt->nb_streams <= 0) { printf("this->avFmtCxt->nb_streams <= 0\n"); return -1; }

    for (unsigned int i = 0; i < avFmtCxt->nb_streams; i++)
    {
        AVStream* as = avFmtCxt->streams[i];
        if (as == NULL)
        {
            printf("format_context->streams[ch] is null\n");
            continue;
        }

        if (as->codec == NULL)
        {
            printf("as->codec is null\n");
            continue;
        }

        if (as->codec->codec_type != AVMEDIA_TYPE_AUDIO)
        {
            printf("other media\n");
            continue;
        }

        printf("sample_rate = %d, channels = %d, sample_format = %d\n",
            as->codec->sample_rate,
            as->codec->channels,
            as->codec->get_format
        );

        return i;

    }

}

#define MAX_DB 91

static inline double logdb(uint64_t v)
{
    double d = v / (double)(0x8000 * 0x8000);
    if (!v)
        return MAX_DB;
    return -log10(d) * 10;
}

/// <summary>
/// 解析声音状态
/// </summary>
/// <param name="ctx"></param>
int handleVolumeStats(AVFilterContext* ctx, VolDetectValue& result)
{
    if (ctx == NULL) { return -1; }

    VolDetectContext* vd = (VolDetectContext*)ctx->priv;
    int i, max_volume, shift;

    uint64_t nb_samples = 0, power = 0, nb_samples_shift = 0, sum = 0;
    uint64_t histdb[MAX_DB + 1] = { 0 };

    for (i = 0; i < 0x10000; i++)
    {
        nb_samples += vd->histogram[i];
    }

    result.nbSamples = nb_samples;
    //printf("n_samples: %" PRId64"\n", nb_samples);
    if (!nb_samples) { return -2; }

    /* If nb_samples > 1<<34, there is a risk of overflow in the
       multiplication or the sum: shift all histogram values to avoid that.
       The total number of samples must be recomputed to avoid rounding
       errors. */
    shift = av_log2(nb_samples >> 33);
    for (i = 0; i < 0x10000; i++)
    {
        nb_samples_shift += vd->histogram[i] >> shift;
        power += (static_cast<unsigned long>(i) - 0x8000) * (static_cast<unsigned long>(i) - 0x8000) * (vd->histogram[i] >> shift);
    }

    if (!nb_samples_shift) { return -2; }

    power = (power + nb_samples_shift / 2) / nb_samples_shift;
    power <= 0x8000 * 0x8000;

    result.power = power;
    result.meanVolumedB = -logdb(power);

    //av_log(ctx, AV_LOG_INFO, "power: %d\n", result.power);
    av_log(ctx, AV_LOG_INFO, "mean_volume: %.1f dB\n", result.meanVolumedB);

    max_volume = 0x8000;
    while (max_volume > 0 && !vd->histogram[0x8000 + max_volume] &&
        !vd->histogram[0x8000 - max_volume])
    {
        max_volume--;
    }

    result.maxVolume = max_volume;
    result.maxVolumedB = -logdb(static_cast<uint64_t>(max_volume) * max_volume);

    //av_log(ctx, AV_LOG_INFO, "max_volume: %d \n", result.maxVolume);
    av_log(ctx, AV_LOG_INFO, "max_volume_db: %.1f dB\n", result.maxVolumedB);

    return 0;
}

int main(int argc, char* argv[])
{
    std::string pWavFilename = "./test.wav";

    if (argc >= 2)
    {
        pWavFilename = argv[1];
    }

    // 初始化FFmpeg
    AVFormatContext* pAvFmtCtx = nullptr; //音视频封装
    AVCodecContext* pCodecCtx = nullptr; //解码器


    AVFilterInOut* outputs = nullptr;
    AVFilterInOut* inputs = nullptr;

    AVFilterContext* pAbuffersrcCtx = nullptr; //声音入口过滤器
    //AVFilterContext* pVolumeFilterCtx = nullptr; //声音增益过滤器
    //AVFilterContext* pAformatFilterCtx = nullptr; //声音格式化过滤器
    //AVFilterContext* pLowpassFilterCtx = nullptr; //声音低通过滤器
    //AVFilterContext* pHighpassFilterCtx = nullptr; //声音高通过滤器
    AVFilterContext* pVolumedetectCtx = nullptr;
    AVFilterContext* pAbuffersinkCtx = nullptr; //声音输出过滤器

    AVFilterGraph* pFilterGraph = nullptr;

    AVPacket* packet = nullptr;
    AVFrame* outAvFrame = nullptr;
    AVFrame* filteredFrame = nullptr;

    int audioStream = -1;
    int ret = 0;
    do
    {
        pAvFmtCtx = avformat_alloc_context();
        if (pAvFmtCtx == NULL)
        {
            printf("Error opening formatCtx\n");
            ret = -1;
            break;
        }

        if (avformat_open_input(&pAvFmtCtx, pWavFilename.c_str(), nullptr, nullptr) != 0)
        {
            printf("Error opening input file\n");
            ret = -1;
            break;
        }

        if (avformat_find_stream_info(pAvFmtCtx, nullptr) < 0)
        {
            printf("Could not find stream information\n");
            ret = -1;
            break;
        }

        audioStream = FindAudioStream(pAvFmtCtx);

        if (audioStream <= -1)
        {
            printf("format_context->streams[audioStream] is null\n");
            ret = -1;
            break;
        } //如果未找到音频,直接结束

        AVStream* as = pAvFmtCtx->streams[audioStream];

        if (as == NULL)
        {
            printf("format_context->streams[audioStream] is null\n", audioStream);
            return -1;
        }

        AVCodec* codec = avcodec_find_decoder(as->codecpar->codec_id);
        if (codec == NULL)
        {
            printf("Could not find AVCodec\n");
            ret = -1;
            break;
        }

        pCodecCtx = avcodec_alloc_context3(codec);
        if (pCodecCtx == NULL)
        {
            printf("Could not find AVCodecContext\n");
            ret = -1;
            break;
        }

        avcodec_parameters_to_context(pCodecCtx, as->codecpar);
        ret = avcodec_open2(pCodecCtx, codec, nullptr);
        if (0 != ret)
        {
            printf("Could not open avcodec_open2\n");
            ret = -1;
            break;
        }
        int numBytes = av_get_bytes_per_sample(pCodecCtx->sample_fmt);
        int sr = pCodecCtx->sample_rate;

        // 创建音频过滤器图
        AVFilterGraph* pFilterGraph = avfilter_graph_alloc();
        if (pFilterGraph == NULL)
        {
            printf("alloc filter graph failed.\n");
            ret = -1;
            break;
        }


        {
            AVBPrint args;
            av_bprint_init(&args, 0, AV_BPRINT_SIZE_AUTOMATIC);
            av_bprintf(&args, "time_base=%d/%d:sample_fmt=%s:sample_rate=%d",
                as->time_base.num, as->time_base.den,
                av_get_sample_fmt_name(as->codec->sample_fmt),
                as->codec->sample_rate);


            if (as->codec->channel_layout <= 0)
            {
                as->codec->channel_layout = AUDIO_INPUT_CHANNEL_LAYOUT;
            }
            char ch_layout[64] = { 0 };
            av_get_channel_layout_string(ch_layout, sizeof(ch_layout), 0, as->codec->channel_layout);
            av_bprintf(&args, ":channel_layout=%s", ch_layout);

            const AVFilter* pAbufferFilter = avfilter_get_by_name("abuffer");
            if (pAbufferFilter == NULL)
            {
                printf("Could not find AVFilter abuffer.\n");
                ret = -1;
                break;
            }

            std::cout << args.str << std::endl;

            ret = avfilter_graph_create_filter(&pAbuffersrcCtx, pAbufferFilter, "in",
                args.str, NULL, pFilterGraph);
        }

        const char* volume_filter_descr = "volumedetect";
        {

            const AVFilter* pVolumedetectFilter = avfilter_get_by_name(volume_filter_descr);
            if (pVolumedetectFilter == NULL)
            {
                printf("Could not find AVFilter volumedetect.\n");
                ret = -1;
                break;
            }

            //连接abuffer和volumedetect滤镜
            avfilter_graph_create_filter(&pVolumedetectCtx, pVolumedetectFilter,
                volume_filter_descr, NULL, NULL, pFilterGraph);

        }

        //输出滤镜
        {
            const AVFilter* pAbuffersinkFilter = avfilter_get_by_name("abuffersink");
            if (pAbuffersinkFilter == NULL)
            {
                printf("Could not find AVFilter abuffersink.");
                ret = -1;
                break;
            }

            avfilter_graph_create_filter(&pAbuffersinkCtx, pAbuffersinkFilter, "out", nullptr, nullptr, pFilterGraph);
        }


        avfilter_link(pAbuffersrcCtx, 0, pVolumedetectCtx, 0);
        avfilter_link(pVolumedetectCtx, 0, pAbuffersinkCtx, 0);

        //为滤镜图中的所有滤镜建立连接
        avfilter_graph_config(pFilterGraph, nullptr);

        if (packet == nullptr)
        {
            packet = (AVPacket*)malloc(sizeof(AVPacket));
        }

        if (packet == nullptr)
        {
            ret = -1;
            break;
        }
        av_init_packet(packet);

        // 读取输入文件并发送数据到过滤器图

        while (av_read_frame(pAvFmtCtx, packet) == 0)
        {
            if (packet->stream_index != audioStream)
            {
                av_packet_unref(packet);
                continue;
            }

            if (!outAvFrame) //检查解析输出帧是否存在
            {
                outAvFrame = av_frame_alloc();
            }

            if (avcodec_send_packet(pCodecCtx, packet) == 0)
            {
                while (avcodec_receive_frame(pCodecCtx, outAvFrame) == 0)
                {
                    if (outAvFrame == NULL)
                    {
                        av_frame_unref(outAvFrame);
                        continue;
                    }

                    // 发送音频帧到过滤器图
                    ret = av_buffersrc_add_frame_flags(pAbuffersrcCtx, outAvFrame, AV_BUFFERSRC_FLAG_KEEP_REF);

                    if (ret == 0)
                    {
                        if (!filteredFrame)
                        {
                            filteredFrame = av_frame_alloc();
                        }

                        av_buffersink_get_frame(pAbuffersinkCtx, filteredFrame);
                        //av_buffersink_get_frame(pAbuffersinkCtx, filteredFrame);
                        //std::cout << filteredFrame.da << std::endl;

                        av_frame_unref(filteredFrame);
                    }

                    av_frame_unref(outAvFrame);
                }
            }

            av_packet_unref(packet);
        }

        AVFilterContext* volumedetectFilter = avfilter_graph_get_filter(pFilterGraph, volume_filter_descr);
        if (!volumedetectFilter)
        {
            printf("volumedetect filter not found\n");
            return -1;
        }
        VolDetectValue volValue;
        ret = handleVolumeStats(volumedetectFilter, volValue);

    } while (0);


    //清理过滤器
    //if (pVolumeFilterCtx) { avfilter_free(pVolumeFilterCtx); }
    //if (pLowpassFilterCtx) { avfilter_free(pLowpassFilterCtx); }
    //if (pHighpassFilterCtx) { avfilter_free(pHighpassFilterCtx); }
    if (pVolumedetectCtx) { avfilter_free(pVolumedetectCtx); }

    if (filteredFrame) { av_frame_free(&filteredFrame); }
    if (outAvFrame) { av_frame_free(&outAvFrame); }

    if (packet) { av_free_packet(packet); av_free(packet); }

    //清理过滤器
    if (pFilterGraph) { avfilter_graph_free(&pFilterGraph); }

    if (pAvFmtCtx) { avformat_close_input(&pAvFmtCtx); }
    if (pCodecCtx) { avcodec_free_context(&pCodecCtx); }

    return ret;
}